using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using DotNetCommon;
using DotNetCommon.Data;
using DotNetCommon.Extensions;
using System.Data.Common;
using System.Linq.Expressions;
using DBUtil.SqlSegment;
using DotNetCommon.Logger;

namespace DBUtil;

/// <summary>
/// 通用数据库访问对象
/// </summary>
public abstract partial class DBAccess(EnumDBType dbType, string dBConnection, DBSetting setting)
{
    private readonly ILogger<DBAccess> logger = LoggerFactory.CreateLogger<DBAccess>();

    #region 设置Settings
    /// <summary>
    /// 设置
    /// </summary>
    public DBSetting Setting { get; private set; } = setting;
    /// <summary>
    /// 获取创建 DBAccess 时添加的配置项, 如果没有则返回 T 的默认值
    /// </summary>
    public T GetExtendSetting<T>(string key) => Setting.GetExtendSetting<T>(key);
    /// <summary>
    /// 尝试获取创建 DBAccess 时添加的配置项
    /// </summary>
    public bool TryGetExtendSetting<T>(string key, out T value) => Setting.TryGetExtendSetting(key, out value);
    /// <summary>
    /// 获取创建 DBAccess 时添加的配置项, 如果没有则返回 null
    /// </summary>
    public object GetExtendSetting(string key) => Setting.GetExtendSetting(key);
    #endregion

    #region 创建DataAdapter
    /// <summary>
    /// 创建DataAdapter
    /// </summary>
    /// <returns></returns>
    protected abstract DataAdapter CreateAdapter(DbCommand cmd);
    #endregion

    #region 基础属性
    /// <summary>
    /// 连接字符串
    /// </summary>
    public string DBConn { get; private set; } = dBConnection;
    /// <summary>
    /// 数据库类型
    /// </summary>
    public EnumDBType DBType { get; private set; } = dbType;

    /// <summary>
    /// 当前数据库使用的参数的前缀符号
    /// </summary>
    public virtual string ParaPrefix { get; }
    #endregion

    #region 一次最大插入行数
    /// <summary>
    /// 当一次插入的数据太多是,尝试分批执行, 以防db报错
    /// <list type="bullet">
    /// <item>sqlserver: 最大可插入1000行</item>
    /// <item>mysql: 无最大行数限制, 但与server的通讯包有限制(ensure 'max_allowed_packet' is greater than)</item>
    /// </list>
    /// </summary>
    public virtual int InsertRowsMaxCountPerBatch => Setting.InsertRowsMaxCountPerBatch ?? -1;
    #endregion

    #region 测试数据库连接
    /// <summary>
    /// 测试数据库连接
    /// </summary>
    public Result OpenTest()
    {
        try
        {
            return RunInSession(Result.Ok);
        }
        catch (Exception ex)
        {
            return Result.NotOk(ex?.Message);
        }
    }

    /// <summary>
    /// 测试数据库连接
    /// </summary>
    public async Task<Result> OpenTestAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            return await RunInSessionAsync(async () => await Task.FromResult(Result.Ok()));
        }
        catch (Exception ex)
        {
            return Result.NotOk(ex?.Message);
        }
    }
    #endregion    

    #region CreatePara 创建参数
    /// <summary>
    /// 创建参数
    /// </summary>
    public abstract DbParameter CreatePara();

    /// <summary>
    /// 创建参数
    /// </summary>
    /// <param name="name">参数名称</param>
    /// <param name="value">参数值</param>
    /// <param name="direction"></param>
    /// <param name="dbType"></param>
    /// <param name="size"></param>
    /// <param name="precision"></param>
    /// <param name="scale"></param>
    public virtual DbParameter CreatePara(string name, object value,
        ParameterDirection? direction = null,
        DbType? dbType = null, int? size = null,
        byte? precision = null, byte? scale = null)
    {
        var para = CreatePara();
        para.ParameterName = name;
        para.Value = value;
        if (direction != null) para.Direction = direction.Value;
        if (dbType != null) para.DbType = dbType.Value;
        if (size != null) para.Size = size.Value;
        if (precision != null) para.Precision = precision.Value;
        if (scale != null) para.Scale = scale.Value;
        return para;
    }
    #endregion    

    #region GetDefaultDbType
    /// <summary>
    /// 获取c#类型对应的默认数据库列类型, 如:
    /// <list type="bucket">
    /// <item>mysql: db.GetDefaultDbType(typeof(int)) => "int"</item>
    /// <item>mysql: db.GetDefaultDbType(typeof(long?)) => "bigint"</item>
    /// <item>mysql: db.GetDefaultDbType(typeof(double)) => "decimal(30,15)"</item>
    /// <item>mysql: db.GetDefaultDbType(typeof(string)) => "text"</item>
    /// <item>mysql: db.GetDefaultDbType(typeof(DateTime)) => "datetime(6)"</item>
    /// </list>
    /// </summary>
    public virtual string GetDefaultDbType(Type type) => Setting.GetDefaultDbtype(type);
    #endregion

    #region 使用SqlBulkCopy批量插入数据
    /// <summary>
    /// 快速插入数据到数据库,对于不支持 BulkCopy 的将改为 insert 执行
    /// </summary>
    /// <param name="dt"></param>
    /// <param name="tableName">表名,如果为空则使用 dt.TableName</param>
    /// <param name="timeoutSeconds">超时描述,默认半小时</param>
    /// <param name="notifyAfter">拷贝多少行后回调通知</param>
    /// <param name="callBack">进度回调,返回false可中断拷贝, 一般返回true</param>
    public abstract void BulkCopy(DataTable dt, string tableName = null, int timeoutSeconds = 60 * 30, int notifyAfter = 0, Func<long, bool> callBack = null);
    #endregion

    #region (异步)使用SqlBulkCopy批量插入数据
    /// <summary>
    /// 快速插入数据到数据库,对于不支持 BulkCopy 的将改为 insert 执行
    /// </summary>
    /// <param name="dt"></param>
    /// <param name="tableName">表名,如果为空则使用 dt.TableName</param>
    /// <param name="timeoutSeconds">超时描述,默认半小时</param>
    /// <param name="notifyAfter">拷贝多少行后回调通知</param>
    /// <param name="callBack">进度回调,返回false可中断拷贝, 一般返回true</param>
    /// <param name="cancellationToken"></param>
    public abstract Task BulkCopyAsync(DataTable dt, string tableName = null, int timeoutSeconds = 60 * 30, int notifyAfter = 0, Func<long, Task<bool>> callBack = null, CancellationToken cancellationToken = default);
    #endregion

    #region 生成分页查询语句: GetSqlForPageSize
    /// <summary>
    /// 生成分页查询语句, 示例:
    /// <code>
    /// db.GetSqlForPageSize("select id,name from person where age>10", "order by id", 10, 1);
    /// //输出:
    /// //select id,name from person where age>10 order by id limit 0,1
    /// </code>
    /// </summary>
    /// <remarks>注意: pageIndex 从1开始</remarks>
    public abstract string GetSqlForPageSize(string selectSql, string orderSql, int pageSize, int pageIndex);
    #endregion    

    #region Id和流水号生成控制器
    public async Task<long> NewIdAsync(string tableName, string colName, CancellationToken cancellationToken = default)
        => await Setting.Generator.NewIdAsync(this, tableName, colName, cancellationToken);
    public async Task<long[]> NewIdsAsync(string tableName, string colName, int count, CancellationToken cancellationToken = default)
        => await Setting.Generator.NewIdsAsync(this, tableName, colName, count, cancellationToken);

    public async Task<string> NewSNOAsync(string tableName, string colName, SerialFormat serialFormat, CancellationToken cancellationToken = default)
        => await Setting.Generator.NewSNOAsync(this, tableName, colName, serialFormat, cancellationToken);
    public async Task<string[]> NewSNOsAsync(string tableName, string colName, SerialFormat serialFormat, int count, CancellationToken cancellationToken = default)
         => await Setting.Generator.NewSNOsAsync(this, tableName, colName, serialFormat, count, cancellationToken);
    #endregion

    #region 分布式锁 RunInLock
    /// <summary>
    /// 在数据库的分布式锁内运行，示例：<br/><br/>
    /// <code>
    /// RunInLock("keystring", () => { /*do something*/});
    /// </code>
    /// </summary>
    /// <remarks>
    /// 实现原理:
    /// <list type="number">
    /// <item>sqlserver: sp_getapplock</item>
    /// <item>mysql: get_lock</item>
    /// </list>
    /// </remarks>
    public void RunInLock(string lock_str, Action action, int getLockTimeoutSecond = 60 * 3)
    {
        AssertUtil.NotNull(lock_str);
        AssertUtil.NotNull(action);
        Setting.Locker.RunInLock(this, lock_str, action, getLockTimeoutSecond);
    }

    /// <summary>
    /// 在数据库的分布式锁内运行，示例：<br/><br/>
    /// <code>
    /// RunInLock("keystring", () => { /*do something*/ return new { id = 1, name = "小明" }; });
    /// </code>
    /// </summary>
    /// <remarks>
    /// 实现原理:
    /// <list type="number">
    /// <item>sqlserver: sp_getapplock</item>
    /// <item>mysql: get_lock</item>
    /// </list>
    /// </remarks>
    public T RunInLock<T>(string lock_str, Func<T> func, int getLockTimeoutSecond = 60 * 3)
    {
        AssertUtil.NotNull(lock_str);
        AssertUtil.NotNull(func);
        return Setting.Locker.RunInLock<T>(this, lock_str, func, getLockTimeoutSecond);
    }

    /// <summary>
    /// 在数据库的分布式锁内运行，示例：<br/><br/>
    /// <code>
    /// await RunInLockAsync("keystring", async () => { /*do something*/});
    /// </code>
    /// </summary>
    /// <remarks>
    /// 实现原理:
    /// <list type="number">
    /// <item>sqlserver: sp_getapplock</item>
    /// <item>mysql: get_lock</item>
    /// </list>
    /// </remarks>
    public async Task RunInLockAsync(string lock_str, Func<Task> func, int getLockTimeoutSecond = 60 * 3)
    {
        AssertUtil.NotNull(lock_str);
        AssertUtil.NotNull(func);
        await Setting.Locker.RunInLockAsync(this, lock_str, func, getLockTimeoutSecond);
    }

    /// <summary>
    /// 在数据库的分布式锁内运行，示例：<br/><br/>
    /// <code>
    /// await RunInLockAsync("keystring", async () => { /*do something*/ return new { id = 1, name = "小明" }; });
    /// </code>
    /// </summary>
    /// <remarks>
    /// 实现原理:
    /// <list type="number">
    /// <item>sqlserver: sp_getapplock</item>
    /// <item>mysql: get_lock</item>
    /// </list>
    /// </remarks>
    public async Task<T> RunInLockAsync<T>(string lock_str, Func<Task<T>> func, int getLockTimeoutSecond = 60 * 3)
    {
        AssertUtil.NotNull(lock_str);
        AssertUtil.NotNull(func);
        return await Setting.Locker.RunInLockAsync<T>(this, lock_str, func, getLockTimeoutSecond);
    }
    #endregion

    #region 数据库管理对象 Manage
    /// <summary>
    /// 数据库管理对象
    /// </summary>
    public abstract DBManage Manage { get; }
    #endregion

    #region 运算符
    private DateTimeSqlSegment dateTimeSqlSegment = null;
    protected abstract DateTimeSqlSegment GetDateTimeSqlSegment();
    public virtual DateTimeSqlSegment DateTimeSqlSegment
    {
        get
        {
            if (dateTimeSqlSegment != null) return dateTimeSqlSegment;
            dateTimeSqlSegment = GetDateTimeSqlSegment();
            return dateTimeSqlSegment;
        }
    }

    private StringSqlSegment stringSqlSegment = null;
    protected abstract StringSqlSegment GetStringSqlSegment();
    /// <summary>
    /// 数据库字符串运算符，示例：
    /// <list type="number">
    /// <item>sqlserver: GetLength("name",true) => "len(ISNULL(null,''))"</item>
    /// <item>sqlserver: AfterString("name","-") => "SUBSTRING(name,(charindex('-',name)+len('-')),LEN(name))"</item>
    /// </list>
    /// </summary>
    public virtual StringSqlSegment StringSqlSegment
    {
        get
        {
            if (stringSqlSegment != null) return stringSqlSegment;
            stringSqlSegment = GetStringSqlSegment();
            return stringSqlSegment;
        }
    }

    private ConvertSqlSegment convertSqlSegment = null;
    protected abstract ConvertSqlSegment GetConvertSqlSegment();

    public virtual ConvertSqlSegment ConvertSqlSegment
    {
        get
        {
            if (convertSqlSegment != null) return convertSqlSegment;
            convertSqlSegment = GetConvertSqlSegment();
            return convertSqlSegment;
        }
    }

    private OtherSqlSegment otherSqlSegment = null;
    protected abstract OtherSqlSegment GetOtherSqlSegment();

    public virtual OtherSqlSegment OtherSqlSegment
    {
        get
        {
            if (otherSqlSegment != null) return otherSqlSegment;
            otherSqlSegment = GetOtherSqlSegment();
            return otherSqlSegment;
        }
    }
    #endregion

    #region 获取刚插入的自增id的Sql语句
    /// <summary>
    /// 获取刚插入的自增id(插入的最后一行的)的Sql语句，原理:
    /// <list type="number">
    /// <item>sqlserver: scope_identity()</item>
    /// <item>mysql: last_insert_id()+{len - 1}</item>
    /// <item>oracle: 只能用序列实现</item>
    /// <item>postgresql: 可以使用serial定义列类型，但实际上也是序列</item>
    /// <item>sqlite: last_insert_rowid()</item>
    /// </list>
    /// 参数: len: 插入的行数,mysql中当一次 insert 多行时, last_insert_id() 返回的是第一行的值
    /// </summary>
    /// <remarks>
    /// 注意: 关于参数len: 因为在mysql中当一次 insert 多行时, last_insert_id() 返回的是第一行的值, 所以用len表示一次插入的行数, 这样返回结果保证是最后一行的id
    /// </remarks>
    public abstract string GetLastInsertedIdSeg(int len = 1);
    #endregion

    #region 判断是否是简单类型
    /// <inheritdoc cref="TypeExtensions.IsSimpleType(Type)"/>
    public static bool IsSimple(Type type) => type.IsSimpleType();
    #endregion

    #region 序列
    /// <summary>
    /// 是否支持序列
    /// </summary>
    /// <returns></returns>
    public abstract bool IsSupportSequence();

    /// <summary>
    /// 获取 下一个序列值 的sql语句
    /// </summary>
    /// <param name="name">序列名称</param>
    /// <returns></returns>
    public abstract string NextSequenceValueSeg(string name);
    #endregion

    #region 发布变动通知
    /// <summary>
    /// 手动触发变动通知,示例:
    /// <code>
    /// //使用 ExecuteSql 更新表数据
    /// db.ExecuteSql("update t_user set name='小明' where id=1");
    /// 
    /// //手动触发变动通知
    /// db.PublishWriteChange(new AfterWriteArgument{ TableName="t_user",WriteType=EnumWriteType.Update,EntityInfo=null });
    /// </code>
    /// </summary>
    public void PublishWriteChange(AfterWriteArgument arg)
    {
        if (!Setting.HasAfterWriteMonitor) return;
        AssertUtil.NotNull(arg.TableName);
        Setting.AfterWriteMonitorAction(arg);
    }
    #endregion

    #region hook
    private readonly Dictionary<string, Func<object, object>> _hooks = [];
    private readonly Dictionary<string, Func<object, object>> _prefixHooks = [];
    protected void InjectHook(string name, Func<object, object> func)
    {
        if (name.StartsWith("prefix:"))
        {
            _prefixHooks[name.Substring("prefix:".Length)] = func;
        }
        else
        {
            _hooks[name] = func;
        }
    }
    internal Func<object, object> GetHook(string name)
    {
        if (_hooks.TryGetValue(name, out Func<object, object> value)) return value;
        return null;
    }
    internal Func<object, object> GetPrefixMatchHook(string name)
    {
        var hook = _prefixHooks.FirstOrDefault(i => name.StartsWith(i.Key));
        return hook.Value;
    }
    #endregion    
}